package com.googlecode.gwtbb.server.dao;

import com.googlecode.gwtbb.client.shared.entity.properties.GetId;
import com.ibatis.sqlmap.client.SqlMapExecutor;
import com.ibatis.sqlmap.client.event.RowHandler;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.orm.ibatis.SqlMapClientCallback;
import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;

import java.sql.SQLException;
import java.util.List;
import java.util.Map;

/**
 * Subclasses should be marked as transactional.
 *
 * @author Abraham Grief
 * @version $Id$
 * @since May 4, 2008
 */
@SuppressWarnings({"unchecked"})
public abstract class DaoIbatis<T extends GetId> extends SqlMapClientDaoSupport implements Dao<T> {
	private static Log log = LogFactory.getLog(DaoIbatis.class);

	public void create(T instance) {
		getSqlMapClientTemplate().insert(getSqlNamespace() + ".create", instance);
	}

	public void create(Iterable<T> instances) {
		getSqlMapClientTemplate().execute(new BatchInsertCallback<T>(getSqlNamespace() + ".create", instances));
	}

	public void createPreIdentified(T instance) {
		getSqlMapClientTemplate().insert(getSqlNamespace() + ".createPreIdentified", instance);
	}

	public void createPreIdentified(Iterable<T> instances) {
		getSqlMapClientTemplate().execute(new BatchInsertCallback<T>(getSqlNamespace() + ".createPreIdentified", instances));
	}

	public void delete(T instance) {
		getSqlMapClientTemplate().delete(getSqlNamespace() + ".delete", instance);
	}

	public Long generateId() {
		// performance improvement: fetch these in batches
		return (Long)getSqlMapClientTemplate().queryForObject(getSqlNamespace() + ".generateId");
	}

	public abstract String getSqlNamespace();

	public void handleAll(EntityHandler<T> entityHandler) {
		getSqlMapClientTemplate().queryWithRowHandler(getSqlNamespace() + ".selectAll", new EntityRowHandler(entityHandler));
	}

	public T select(Long id) {
		return (T)getSqlMapClientTemplate().queryForObject(getSqlNamespace() + ".select", id);
	}

	public List<T> selectAll() {
		return (List<T>)getSqlMapClientTemplate().queryForList(getSqlNamespace() + ".selectAll");
	}

	public int update(Map<String, Object> properties) {
		return getSqlMapClientTemplate().update(getSqlNamespace() + ".update", properties);
	}

	public int update(Iterable<Map<String, Object>> properator) {
		return ((Number)getSqlMapClientTemplate().execute(new BatchUpdateCallback<Map<String, Object>>(getSqlNamespace() + ".update", properator))).intValue();
	}

	public class EntityRowHandler implements RowHandler {
		private final EntityHandler<T> handler;

		public EntityRowHandler(EntityHandler<T> handler) {
			this.handler = handler;
		}

		public void handleRow(Object o) {
			try {
				handler.handle((T)o);
			} catch (Exception e) {
				log.error("could not handle object: " + o, e);
			}
		}
	}

	protected abstract static class BatchCallback<T> implements SqlMapClientCallback {
		protected final String statement;
		protected final Iterable<T> instances;

		public BatchCallback(String statement, Iterable<T> instances) {
			this.statement = statement;
			this.instances = instances;
		}

		public Object doInSqlMapClient(SqlMapExecutor executor)
			throws SQLException {
			executor.startBatch();
			for (T instance : instances)
				execute(executor, instance);
			executor.executeBatch();
			return returnValue();
		}

		public abstract void execute(SqlMapExecutor executor, T instance) throws SQLException;

		public abstract Object returnValue();
	}

	protected static class BatchInsertCallback<T> extends BatchCallback<T> {
		public BatchInsertCallback(String statement, Iterable<T> instances) {
			super(statement, instances);
		}

		public void execute(SqlMapExecutor executor, T instance) throws SQLException {
			executor.insert(statement, instance);
		}

		public Object returnValue() {
			return null;
		}
	}

	protected static class BatchUpdateCallback<T> extends BatchCallback<T> {
		int updateTotal = 0;

		public BatchUpdateCallback(String statement, Iterable<T> instances) {
			super(statement, instances);
		}

		public void execute(SqlMapExecutor executor, T instance) throws SQLException {
			updateTotal += executor.update(statement, instance);
		}

		public Object returnValue() {
			return updateTotal;
		}
	}
}
